#!/bin/bash
#
# chkconfig: - 21 08
# description: Loads and unloads the drbd module
#
# Copyright 2001-2013 LINBIT
#
# Philipp Reisner, Lars Ellenberg
#
### BEGIN INIT INFO
# Provides: drbd
# Required-Start: $local_fs $network $syslog
# Required-Stop:  $local_fs $network $syslog
# Should-Start:   sshd multipathd
# Should-Stop:    sshd multipathd
# Default-Start:
# Default-Stop:   0 1 6
# X-Start-Before: heartbeat corosync
# X-Stop-After:   heartbeat corosync
# X-Interactive:  true
# Short-Description:    Control DRBD resources.
# Description:    Control all DRBD resources.
#	You SHOULD NOT enable this init script
#	when using a cluster manager such as Pacemaker.
#	Start will try to:
#	  load the DRBD driver module,
#	  configure (bring up as Secondary) all DRBD resources as described in
#	  the config file, and promote those resources where the
#	  "become-primary-on" config statement matches.
#	Stop will try to:
#	  demote and unconfigure all DRBD resources described in the config
#	  file, and remove the module.
### END INIT INFO

DEFAULTFILE="/etc/default/drbd"
DRBDADM="drbdadm"
DRBDSETUP="drbdsetup"
PROC_DRBD="/proc/drbd"
MODPROBE="/sbin/modprobe"
RMMOD="/sbin/rmmod"
UDEV_TIMEOUT=10
ADD_MOD_PARAM=""

PATH=/usr/sbin:/sbin:$PATH

if [ -f $DEFAULTFILE ]; then
  . $DEFAULTFILE
fi

# we only use these two functions, define fallback versions of them ...
log_daemon_msg() { echo -n "${1:-}: ${2:-}"; }
log_end_msg() { echo "."; }
# ... and let the lsb override them, if it thinks it knows better.
if [ -f /lib/lsb/init-functions ]; then
    . /lib/lsb/init-functions
fi

assure_module_is_loaded()
{
    [ -e "$PROC_DRBD" ] && return

    $MODPROBE -s drbd $ADD_MOD_PARAM || {
	echo "Can not load the drbd module."$'\n'
	exit 5 # LSB for "not installed"
    }
    # tell klogd to reload module symbol information ...
    [ -e /var/run/klogd.pid ] && [ -x /sbin/klogd ] && /sbin/klogd -i
}

drbd_pretty_status()
{
	local proc_drbd=$1
	# add resource names
	if ! type column &> /dev/null ||
	   ! type paste &> /dev/null ||
	   ! type join &> /dev/null ||
	   ! type sed &> /dev/null ||
	   ! type tr &> /dev/null
	then
		cat "$proc_drbd"
		return
	fi
	sed -e '2q' < "$proc_drbd"
	sed_script=$(
		i=0;
		_sh_status_process() {
			let i++ ;
			stacked=${_stacked_on:+"^^${_stacked_on_minor:-${_stacked_on//[!a-zA-Z0-9_ -]/_}}"}
			printf "s|^ *%u:|%6u\t&%s%s|\n" \
				$_minor $i \
				"${_res_name//[!a-zA-Z0-9_ -]/_}" "$stacked"
		};
		eval "$(drbdadm sh-status)" )

	p() {
		sed -e "1,2d" \
		      -e "$sed_script" \
		      -e '/^ *[0-9]\+: cs:Unconfigured/d;' \
		      -e 's/^\(.* cs:.*[^ ]\)   \([rs]...\)$/\1 - \2/g' \
		      -e 's/^\(.* \)cs:\([^ ]* \)st:\([^ ]* \)ds:\([^ ]*\)/\1\2\3\4/' \
		      -e 's/^\(.* \)cs:\([^ ]* \)ro:\([^ ]* \)ds:\([^ ]*\)/\1\2\3\4/' \
		      -e 's/^\(.* \)cs:\([^ ]*\)$/\1\2/' \
		      -e 's/^ *[0-9]\+:/ x &??not-found??/;' \
		      -e '/^$/d;/ns:.*nr:.*dw:/d;/resync:/d;/act_log:/d;' \
		      -e 's/^\(.\[.*\)\(sync.ed:\)/... ... \2/;/^.finish:/d;' \
		      -e 's/^\(.[0-9 %]*oos:\)/... ... \1/' \
		      < "$proc_drbd" | tr -s '\t ' '  ' 
	}
	m() {
		join -1 2 -2 1 -o 1.1,2.2,2.3 \
			<( ( drbdadm sh-dev all ; drbdadm -S sh-dev all ) | cat -n | sort -k2,2) \
			<(sort < /proc/mounts ) |
			sort -n | tr -s '\t ' '  ' | sed -e 's/^ *//'
	}
	# echo "=== p ==="
	# p
	# echo "=== m ==="
	# m
	# echo "========="
	# join -a1 <(p|sort) <(m|sort)
	# echo "========="
	(
	echo m:res cs ro ds p mounted fstype
	join -a1 <(p|sort) <(m|sort) | cut -d' ' -f2-6,8- | sort -k1,1n -k2,2
	) | column -t
}

# Try to settle regardless of udev version or presence,
# so "/etc/init.d/drbd stop" is able to rmmod, without interfering
# temporary module references caused by udev scanning the devices.
# But don't wait too long.
_udev_settle()
{
	if udevadm version ; then
		# ok, we have udevadm, use it.
		udevadm settle --timeout=5
	else
		# if udevsettle is not there,
		# no matter.
		udevsettle --timeout=5
	fi
}

# Set up/detach linstor loopback devices for LINSTOR file based pools
# FIXME I think setting up these loop device mappings should be done elsewhere,
# and not in the DRBD init file; possibly we need proper support in drbdadm.
handle_linstor_loopback()
{
	local line dev file loop_mapping

	# new location
	loop_mapping=/var/lib/linstor.d/loop_device_mapping
	# fallback to old location
	[ -f "$loop_mapping" ] || loop_mapping=/var/lib/linstor/loop_device_mapping

	# || [[ -n $line ]]: in case there is no newline at EOF
	while read -r line || [[ -n $line ]] ; do
		dev=${line%%:*}
		file=${line#*:}
		test -f "$file" && test -r "$file" && test -w "$file" || continue
		# should -f be allowed?
		[[ $dev = loop* ]] || [[ $dev = /dev/loop* ]] || continue
		case "$1" in
			# what about existing, but "wrong", mappings?
			start) losetup "$dev" 2>/dev/null || losetup "$dev" "$file";;
			stop)  losetup "$dev" 2>/dev/null && losetup -d "$dev";;
		esac
	done < "$loop_mapping"

	# at least we tried.
	return 0
}

run_hook()
{
	n="hook_$1"
	if t=$(type -t "$n") && [[ "$t" == "function" ]] ; then
		shift
		"$n" "$@"
	fi
}

case "$1" in
    start)
	# no module, no DRBD.
	assure_module_is_loaded

	# Just in case drbdadm want to display any errors in the configuration
	# file, or we need to ask the user about registering this installation
	# at http://usage.drbd.org, we call drbdadm here without any IO
	# redirection.
	# If "no op" has a non-zero exit code, the config is unusable,
	# and every other command will fail.
	out=$($DRBDADM sh-nop 2>&1); ex=$?
	[[ $ex = 127 ]] && exit 5 # LSB for "not installed"
	log_daemon_msg "Starting DRBD resources"
	if [[ $ex != 0 ]] ; then
	    printf "\n%s\n" "$out" >&2
	    log_end_msg 1
	    exit 6 # LSB for "not configured"
	fi

	handle_linstor_loopback "$1"
	$DRBDADM adjust-with-progress all
	[[ $? -gt 1 ]] && exit 20

	# make sure udev has time to create the device files
	# FIXME this probably should, on platforms that have it,
	# use udevadm settle --timeout=X --exit-if-exists=$DEVICE
	for DEVICE in `$DRBDADM sh-dev all`; do
	    UDEV_TIMEOUT_LOCAL=$UDEV_TIMEOUT
	    while [ ! -e $DEVICE ] && [ $UDEV_TIMEOUT_LOCAL -gt 0 ] ; do
		sleep 1
		UDEV_TIMEOUT_LOCAL=$(( $UDEV_TIMEOUT_LOCAL-1 ))
	    done
	done

	[ -d /var/lock/subsys ] && touch /var/lock/subsys/drbd	# for RedHat
	run_hook start_before-wait
	$DRBDADM wait-con-int # User interruptible version of wait-connect all
	run_hook start

	# Become primary if configured
	# Currently, this is necessary for drbd8
	# drbd9 supports automatic promote and removes the "sh-b-pri" command.
	$DRBDADM help sh-b-pri &> /dev/null && $DRBDADM sh-b-pri all || true
	log_end_msg 0

	# Now handle stacked devices, if any
	STACKED_RESOURCES=""
	# no point trying stacking, if we don't have at least one primary.
	if grep -w Primary /proc/drbd &> /dev/null; then
	    # heuristic: don't mess with dinosaurs # :-(
	    if ! grep -Ee '\<drbddisk|drbdupper\>' /etc/ha.d/haresources &> /dev/null; then
		STACKED_RESOURCES=`$DRBDADM -S sh-resources`
	    fi
	fi
	if [[ $STACKED_RESOURCES ]] ; then
	    log_daemon_msg "Starting stacked DRBD resources"
	    DEVICES=`$DRBDADM -S sh-dev all`
	    $DRBDADM -S adjust-with-progress all
	    if [[ $? -gt 1 ]] ; then
		log_end_msg 1
	    else
		for DEVICE in $DEVICES; do
		    UDEV_TIMEOUT_LOCAL=$UDEV_TIMEOUT
		    while [ ! -e $DEVICE ] && [ $UDEV_TIMEOUT_LOCAL -gt 0 ] ; do
			sleep 1
			UDEV_TIMEOUT_LOCAL=$(( $UDEV_TIMEOUT_LOCAL-1 ))
		    done
		done
		$DRBDADM -S wait-con-int # User interruptible version of wait-connect all
		$DRBDADM help sh-b-pri &> /dev/null && $DRBDADM -S sh-b-pri all || true
		log_end_msg 0
	    fi
	fi

	;;
    stop)
	$DRBDADM sh-nop
	[[ $? = 127 ]] && exit 5 # LSB for "not installed"
	log_daemon_msg "Stopping all DRBD resources"
	for try in 1 2; do
	    stop_failed=0
	    if [ -e $PROC_DRBD ] ; then
		[[ $try = 2 ]] && echo "Retrying once..."
		# bypass drbdadm and drbd config file and everything,
		# to avoid leaving devices around that are not referenced by
		# the current config file, or in case the current config file
		# does not parse for some reason.
		for d in /dev/drbd* ; do
			[ -L "$d" ] && continue
			[ -b "$d" ] || continue
			M=$(umount "$d" 2>&1)
			case $M in
			*" not mounted") :;;
			*) stop_failed=1; echo "$M" >&2 ;;
			esac
		done
		for res in $($DRBDADM --stacked sh-resources) $($DRBDADM sh-resources); do
			$DRBDSETUP down "$res" || stop_failed=1
		done
		handle_linstor_loopback "$1"
		_udev_settle &> /dev/null
		if [ -d /sys/module/drbd/holders ]; then
			(cd /sys/module/drbd/holders; for tr in *; do [ -d ${tr} ] && ${RMMOD} ${tr}; done)
		fi
		if grep -qw drbd /proc/modules; then
			$RMMOD drbd || stop_failed=1
		fi
		[ $stop_failed = 0 ] && break
	    fi
	done
	run_hook stop
	[ -f /var/lock/subsys/drbd ] && rm /var/lock/subsys/drbd
	log_end_msg 0
	;;
    status)
	# NEEDS to be heartbeat friendly...
	# so: put some "OK" in the output.
	if [ -e $PROC_DRBD ]; then
	    echo "drbd driver loaded OK; device status:"
	    drbd_pretty_status $PROC_DRBD 2>/dev/null
	    exit 0
	else
	    echo >&2 "drbd not loaded"
	    exit 3
	fi
	;;
    reload)
	$DRBDADM sh-nop
	[[ $? = 127 ]] && exit 5 # LSB for "not installed"
	log_daemon_msg  "Reloading DRBD configuration"
	$DRBDADM adjust all
	run_hook reload
	log_end_msg 0
	;;
    restart|force-reload)
	( . $0 stop )
	( . $0 start )
	;;
    *)
	echo "Usage: /etc/init.d/drbd {start|stop|status|reload|restart|force-reload}"
	exit 1
	;;
esac

exit 0
